﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Web.Configuration;
using InstaSharp.Models;
using Newtonsoft.Json.Linq;
using System.Collections;
using System.Diagnostics;
using Twitterizer;
using VkontakteSharp.Model;

public class Mapper {
        public static object Map<T>(string json) where T : new() {
            
            var t = typeof(T);
            try {
                 
                object instance;
                if (t.Name == typeof(List<>).Name || t.Name == typeof(IEnumerable<>).Name)
                //if (t.GetInterface("IList") != null || t.GetInterface("IEnumerable") != null)
                {
                    JArray j = JArray.Parse(json);
                    //IList<sometype>
                    instance = Map(t, j);
                }
                else
                {
                    JObject j = JObject.Parse(json);
                    instance = Map(t, j);
                }

                // add the pure json back
                if (instance != null) {
                    var prop = instance.GetType().GetProperty("Json");
                    if (prop != null) {
                        prop.SetValue(instance, json, null);
                    }
                    prop = instance.GetType().GetProperty("Meta");
                    if (prop != null)
                    {
                        Meta realMeta = prop.GetValue(instance, null) as Meta;
                        if (realMeta == null)
                        {
                            prop.SetValue(instance, Map<Meta>(json), null);
                        }
                    }
                }

                return instance;
            
            } 
            catch (Exception ex) {
                Debug.WriteLine(ex.Message+ " @ "+json);
                return null;
            }
        }

        private static object Map(Type t, JObject json) {

                var instance = Activator.CreateInstance(t);

                Array.ForEach(instance.GetType().GetProperties(), prop => {

                    var attribute = prop.GetCustomAttributes(typeof(JsonMapping), false);

                    if (attribute.Length > 0) {
                        var propertyType = prop.PropertyType;
                        var mapsTo = ((JsonMapping)attribute[0]).MapsTo;
                        var mappingType = ((JsonMapping)attribute[0]).MapType;

                        switch (mappingType) {
                            case JsonMapping.MappingType.Class:
                                if (json[mapsTo] != null)
                                {
                                    if (propertyType == typeof (Coordinates))
                                    {
                                        string[] parts = json[mapsTo].ToString().Split(' ');
                                        var coords = new Coordinates
                                        {
                                            Latitude = double.Parse(parts[0], CultureInfo.InvariantCulture.NumberFormat),
                                            Longitude = double.Parse(parts[1], CultureInfo.InvariantCulture.NumberFormat)
                                        };
                                        prop.SetValue(instance, coords, null);
                                    }
                                    else if (json[mapsTo].HasValues)
                                    {
                                        var obj = Map(propertyType, (JObject) json[mapsTo]);
                                        prop.SetValue(instance, obj, null);
                                    }
                                }
                                break;
                            case JsonMapping.MappingType.Collection:
                                var col = Map(propertyType, (JArray)json[mapsTo]);
                                prop.SetValue(instance, col, null);
                                break;
                            default:
                                if (json != null) {
                                    if (json[mapsTo] != null) {
                                        // special case for datetime because it comes in Unix format
                                        if (propertyType == typeof(DateTime))
                                        {
                                            prop.SetValue(instance, UnixTimeStampToDateTime(json[mapsTo].ToString()),
                                                          null);
                                        }else if (propertyType == typeof (DateTimeOffset))
                                        {
                                            prop.SetValue(instance, DateTimeOffset.Parse(json[mapsTo].ToString()),
                                                              null);
                                        }
                                        else if (propertyType.IsGenericType &&
                                                 propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
                                        {
                                            if (json[mapsTo] == null || String.IsNullOrEmpty(json[mapsTo].ToString())
                                                || String.Compare(json[mapsTo].ToString(),
                                                    "null", StringComparison.OrdinalIgnoreCase) == 0)
                                            {
                                                //случай, когда для типа Nullable передается строковый параметр Null
                                                prop.SetValue(instance, null, null);
                                            }
                                            else
                                            {
                                                prop.SetValue(instance,
                                                            Convert.ChangeType(json[mapsTo].ToString(), propertyType.GetGenericArguments()[0]),
                                                            null);
                                            }
                                        }
                                        else
                                        {
                                            prop.SetValue(instance,
                                                          Convert.ChangeType(json[mapsTo].ToString(), prop.PropertyType),
                                                          null);
                                        }
                                    }
                                }
                                break;
                        }
                    }
                });

                return instance;
        }

        private static IList Map(Type t, JArray json)
        {
            while (t.GetGenericArguments().Length == 0)
            {
                t = t.BaseType;
                if (t == null)
                {
                    throw new ArgumentNullException("t", "JSON: Попытка привести класс к типу массива");
                }
            }
            var type = t.GetGenericArguments()[0];
            // This will produce List<Image> or whatever the original element type is
            var listType = typeof(List<>).MakeGenericType(type);
            var result = (IList)Activator.CreateInstance(listType);

            if (json != null) {
                foreach (var j in json)
                {
                    if (type.Name == "String" || type.Name == "Int32")
                        result.Add(j.ToString());
                    else if (j.GetType() == typeof (JValue))
                    {
                        //например явное указание числа записей в массиве (ох уж этот вконтакт)
                        Debug.WriteLine("JArray skipped: " + j);
                    }else if (type == typeof(Attachment))
                    {
                        result.Add(Attachment.GetAttachment((JObject)j));
                    }
                    else{ 
                        result.Add(Map(type, (JObject) j));
                    }
                }
            }

            return result;
        }

        private static DateTime UnixTimeStampToDateTime(string unixTimeStamp) {
            // Unix timestamp is seconds past epoch
            double unixTime;
            if (double.TryParse(unixTimeStamp,out unixTime))
            {
                Convert.ToDouble(unixTimeStamp);
                System.DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0);
                dtDateTime = dtDateTime.AddSeconds(unixTime);//Чтобы время было московское//.ToLocalTime();//NOTE: весьма спорное решение =((
                return dtDateTime;
            }
            return DateTime.MinValue;
        }

        private static void SetPropertyValue(PropertyInfo prop, object instance, object value) {
            prop.SetValue(instance, Convert.ChangeType(value, prop.PropertyType), null);
        }
    }
